#ifndef _REDIX_H_
#define _REDIX_H_

#include <thread>
#include <queue>
#include <unordered_map>

#include "hiredis/hiredis.h"

#include "command.h"

static const std::string REDIS_DEFAULT_HOST = "localhost";
static const int REDIS_DEFAULT_PORT = 6379;

class Redix {
    public:
	typedef Command::Callback	Callback;
	static const int NOT_CONNECTED_YET = 0;
	static const int CONNECTED = 1;
	static const int DISCONNECTED = 2;
	static const int CONNECT_ERROR = 3;
	static const int DISCONNECT_ERROR = 4;
	static const int INIT_ERROR = 5;

	Redix();
	~Redix();

	bool connect(const std::string& host = REDIS_DEFAULT_HOST, const int port = REDIS_DEFAULT_PORT);

	void disconnect();

	void stop();

	void wait();

	void command(const std::string& cmd, const Callback& callback = nullptr);

	// run in main tread
	void runInLoop();

    public:
	redisAsyncContext *m_ctx;
	std::string m_host;
	int m_port;
    private:
	bool initLibev();
	bool initHiredis();

	static void connectCallback(const redisAsyncContext* c, int status);
	static void disconnectCallback(const redisAsyncContext* c, int status);

	// run in a separate thread
	void runEventLoop();

	Command* findCommand(long id);

	static bool submitToServer(Command* c);

	static void commandCallback(redisAsyncContext* ctx, void* r, void* privdata);

	static void processQueuedCommands(struct ev_loop* loop, ev_async* async, int revents);

	bool processQueuedCommand(long id);

	static void processCompletedCommands(struct ev_loop* loop, ev_async* async, int revents);

	bool processCompletedCommand(long id);

	static void freeQueuedCommands(struct ev_loop* loop, ev_async* async, int revents);

	bool freeQueuedCommand(long id);

	void deregisterCommand(const long id);

	long freeAllCommands();

	int getConnectState();

	void setConnectState(int state);

	int getRunning();

	void setRunning(bool running);

	int getExited();

	void setExited(bool exited);

    private:
	int m_connectState = NOT_CONNECTED_YET;
	std::mutex m_connMutex;
	std::condition_variable m_connCond;

	struct ev_loop* m_evLoop;

	ev_async m_cmdWatcher;
	ev_async m_stopWatcher;
	ev_async m_freeWatcher;

	std::thread m_evloopThread;

	bool m_running = false;
	std::mutex m_runMutex;
	std::condition_variable m_runCond;

	std::atomic_bool m_toExit = {false};
	bool m_exited = false;
	std::mutex m_exitMutex;
	std::condition_variable m_exitCond;

	std::unordered_map<long, Command*> m_commands;
	std::mutex m_commandMutex;

	std::queue<long> m_preQueue;
	std::mutex m_preMutex;

	std::queue<long> m_completedQueue;
	std::mutex m_completedMutex;

	std::queue<long> m_freeQueue;
	std::mutex m_freeMutex;

};

#endif
